/*
 	pulse.cc

 	Copyright (c) 2008 Bojan Rajkovic

 	Permission is hereby granted, free of charge, to any person obtaining a copy
 	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
 	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 	copies of	 the Software, and to permit persons to whom the Software is
 	furnished to do so, subject to the following conditions:
	
 	The above copyright notice and this permission notice shall be included in
 	all copies or substantial portions of the Software.

 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 	THE SOFTWARE.
*/

#include "../include/pulse.h"

// pulse_to_web returns the HTTP status code--ideally you want to get back a 200 OK.
// if we failed somewhere along the way, we get back a -1
unsigned int pulse::pulse_to_web(pulse_info pulse) {
		
	// set up our variables
	int client_socket, status_code;
  	struct sockaddr_in serverAddress;
	struct hostent *hostInfo;
	const char* pulse_server;
	char* message; // generic asprintf buffer
	unsigned int pulse_server_port;
	
	// Pull the important stuff out of the configuration file
	Config config;
	config.readFile(strdup(DEFAULT_CONFIG_FILE));
	pulse_server = config.lookup("application.pulse.server");
	pulse_server_port = config.lookup("application.pulse.port");
	
	client_socket = socket(PF_INET, SOCK_STREAM, 0);
	FILE* socket_file = fdopen(client_socket, "r"); // we'll want this later
	
	if (client_socket < 0) {
		utility::log_message(_("Could not open socket to talk to pulse server, will not pulse now."), M_ERROR);
		return -1;
	}

	hostInfo = gethostbyname(pulse_server);

	if (hostInfo == NULL) {
		char *message;
		asprintf(&message, _("gethostbyname() failed to interpret %s, will not pulse now."), pulse_server);
		utility::log_message(message, M_ERROR);
		free(message);
		return -1;
	}

	serverAddress.sin_family = hostInfo->h_addrtype;
	memcpy(&serverAddress.sin_addr.s_addr, hostInfo->h_addr_list[0], hostInfo->h_length);
  	serverAddress.sin_port = htons(pulse_server_port);

	if ( connect(client_socket, (const sockaddr*) &serverAddress, sizeof(serverAddress)) < 0) {
		utility::log_message(_("Could not connect socket to server, will not pulse now."), M_ERROR);
		return -1;
	}

	/* now let's build what we'll be asking the server for */
	char* requestString;
	asprintf(&requestString, PULSE_REQUEST, pulse.user_name, utility::sha1(pulse.password), pulse.keys, 
		pulse_server, strdup(KEYCOUNTER_VERSION));
	utility::log_message(requestString, M_INFO);

	if (send(client_socket, requestString, strlen(requestString)+1, 0) < 0) {
		utility::log_message(_("Could not send data across socket to server, will not pulse now."), M_ERROR);
		return -1;
	}
	
	free(requestString);

	char receiveBuffer[4096]; // we're not gonna get any more than 4kb of characters per line, at least I hope not

	// we're expecting HTTP/1.x ### where ### is a status code.
	// hopefully, 200 is what we get there, but we may not.
	if (fgets(receiveBuffer, 4096, socket_file) != NULL) {
		// let's use pcre to extract the status code
	        RE re("(\\d{3})");
            	re.PartialMatch(receiveBuffer, &status_code);
	}

	switch (status_code) {
		case HTTP_STATUS_CODE_OK: //200
			return HTTP_STATUS_CODE_OK;
		// we really should never be seeing these codes, unless the web service is moving to a different
		// hostname, because we really don't have the functionality to change our entire URL because the
		// server operator wants to use a different uri. let him write a redirect server-side for that.
		// we'll log them for posterity, and move on, because they may be useful someday.
		// Also, we're handling these here because the server actually needs to give us some information with
		// these. Other status codes don't have any accompanying information, so we'll let handle_http_error()
		// deal with them.
		case HTTP_STATUS_CODE_MOVED_PERMANENTLY: //301
		case HTTP_STATUS_CODE_FOUND: // 302
		case HTTP_STATUS_CODE_SEE_OTHER: //303
		case HTTP_STATUS_CODE_MOVED_TEMPORARILY:  {//307
			// okay, so we know the status code, and we know we should be seeing a location header
			// let's read a line at a time, and use regular expressions to look for the Location: header
			// editor's note: i fucking love regexp's and pcrecpp.
			char *header_name, *uri;
			while (fgets(receiveBuffer, 4096, socket_file) != NULL) {
				// let's see what header we've got. we're gonna use a regex for this again
				// the regex :.*$ will get us the header name.
				RE re("(:.*$)");
				re.PartialMatch(receiveBuffer, header_name);
				if (strcmp(header_name,strdup("Location")) == 0) { // okay, so we've got the location header
					// the regex ^\w+: will get us the location uri
					RE re("(^.+:?)");
					re.PartialMatch(receiveBuffer, uri);
				}
			}
			if (uri == NULL) {
				uri = strdup(_("Location header not found, URI not set."));
			}
			asprintf(&message, _("Got a 30x error code from the server: it says %i, new URI %s"), status_code, uri);
			utility::log_message(message, M_ERROR);
			free(message);
			free(uri);
			free(header_name);
			close(client_socket);
			fclose(socket_file);
			return status_code;
			break;
		}
		default:
			pulse::handle_http_error(status_code);
			close(client_socket);
			fclose(socket_file);
			return status_code;
	}
	return status_code;
}

void pulse::zero_pulse_file() {
	// we want to just remove the old file, or rather open it with ios::trunc so it gets wiped and write 0 to it.
	// the pulse file is just a storage of the number of keys since the last pulse, updated whenever we get any of
	// the shutdown signals.
	ofstream pulse_file("/var/lib/keycounter/pulse", ios::trunc);
	pulse_file << 0;
	pulse_file.close();
}

void pulse::handle_http_error(int error_code) {
	char* message;
	switch (error_code) {
		// These we're actually handling here.
		case HTTP_STATUS_CODE_BAD_REQUEST: //400
			utility::log_message(_("Tried to pulse, server told us 400 Bad Request, you should probably file a bug."), M_ERROR);
			break;
		case HTTP_STATUS_CODE_FORBIDDEN: //402
			utility::log_message(_("Tried to pulse, server told us 402 Forbidden, you should check with your pulse server owner."), M_ERROR);
			break;
		case HTTP_STATUS_CODE_NOT_FOUND: //404 /* in essence, this and 410 are the same */
			utility::log_message(_("Tried to pulse, server told us 404 Not Found, you should check with your pulse server owner."), M_ERROR);
			break;
		case HTTP_STATUS_CODE_GONE: //410
			utility::log_message(_("Tried to pulse, server told us 410 Gone (similar to 404), you should check with your pulse server owner."), M_ERROR);
			break;
		default:
			asprintf(&message, _("Server returned error code %i, please check its meaning and check on it with your pulse server owner."), error_code);
			utility::log_message(message, M_ERROR);
			break;
	}
	free(message);
}

void pulse::save_pulse_file(unsigned long keys) {
	ofstream of("/var/lib/keycounter/pulse", ios::trunc);
	of << keys;
	of.close();
}

// pulse_to_db returns the sqlite3 return value, which can be checked too.
// can also return -1 if there were errors
unsigned int pulse::pulse_to_db(pulse_info pulse) {
	char* message;
	Config config;
	config.readFile(strdup(DEFAULT_CONFIG_FILE));
	const char* db = config.lookup("application.pulse.db");
	int retval, exec_retval;
	try {
		sqlite_wrapper database(db);
		free((void*)db);
		retval = database.table_exists("pulse_logs");

		switch (retval) {
			case E_DB_NOT_OPEN: {
				utility::log_message(_("Database not open though we have an instance, there is something quite wrong with that."), M_ERROR);
				return -1;
			}
			case 0: {
				// okay, so we don't have our table, let's create it
				const char *sql = "create table pulse_log (key INTEGER PRIMARY KEY, keys_pulsed INTEGER, timeEntered INTEGER";
				exec_retval = database.exec(sql);
				free((void*)sql);
				if (exec_retval != 0) { // aw crap, we didn't create it
					asprintf(&message, _("Could not create table pulse_log in database, SQLite error: %d"), exec_retval);
					utility::log_message(message, M_ERROR);
					free(message);
					return exec_retval;
				}
				/* if we get here, then goody, we created it, press on! */
				break;
			}
			default: { // oh shit. something's wrong, retval > 0
				asprintf(&message, _("Could not check if the table exists, something's seriously wrong. SQLite gave error %d"), retval);
				utility::log_message(message, M_ERROR);
				free(message);
				return retval;
			}
		} // okay, so we've made sure that we either a) have a table or b) have created it if we didn't have it

		// now that we know the database is open, and we have our table, let's just push that sucker in
		char* sql;
		asprintf(&sql, "insert into pulse_log (keys_pulsed, timeEntered) values (%li, %i);", 
			pulse.keys, (int)time(NULL));
		exec_retval = database.exec(sql);
		free(sql);
		
		if (exec_retval == 0) {
			asprintf(&message, _("Inserted pulse into database succesfully at %s"), 
				utility::get_current_date_time());
			utility::log_message(message, M_INFO);
			free(message);
			return 0;
		} else {
			asprintf(&message, _("Could not insert pulse into database, SQLite gave error: %d (%s)"), exec_retval, database.get_last_error());
			utility::log_message(message, M_ERROR);
			free(message);
			return exec_retval;
		}	
	} catch (database_open_failed_exception& e) {
		return -1;
	}
}
